local XMPQT = XLGetGlobal("XMPQT")
XMPQT.Output.Info("=================================================")
XMPQT.Output.Title("测试项目：播放格式支持覆盖测试") -- 格式要unicode

local tipUtil = XLGetObject("XmpTipWnd.Util")
local tipAsynUtil = XLGetObject("XmpTipWnd.AsynUtil")
local xlData = XLGetObject("XmpTipWnd.XLData")
local XMPQT = XLGetGlobal("XMPQT")

local nThisLuaVersion_4 = 2
local strThisLuaVersion = tostring(nThisLuaVersion_4)
local strBusinessName = "TestOnFly"

--******************* Utilities **************************

function Log(strMsg)
	if type(tipUtil.Log) == "function" then
		tipUtil:Log("@@" .. tostring(strBusinessName) .. "[" .. tostring(strThisLuaVersion) .. "]  " .. tostring(strMsg))
	end
end

function XLMB(strMsg)
	XMPQT.Output.Info(strMsg)
end

function GTV(obj)
	return "[" .. type(obj) .. "`" .. tostring(obj) .. "]"
end

function IsRealString(str)
	return type(str) == "string" and str ~= ""
end

function IsNumberString(str)
	return IsRealString(str) and type(tonumber(str)) == "number"
end

function Time2String(nUTCInSec)
	local strTime = ""
	if type(nUTCInSec) == "number" and nUTCInSec > 0 then
		local iYear, iMonth, iDay, iHour, iMinute, iSecond, iDayofWeek = tipUtil:FormatCrtTime(nUTCInSec)
		strTime = tostring(iYear) .. "/" .. tostring(iMonth) .. "/" .. tostring(iDay)
			.. "-" .. tostring(iHour) .. ":" .. tostring(iMinute) .. ":" .. tostring(iSecond)
			.. "(" .. tostring(nUTCInSec) .. " UTC secs)"
	end
	return strTime
end

function List2String(tList)
	local strResult = GTV(tList)
	if type(tList) == "table" then
		strResult = strResult .. "{"
		
		if #tList >= 1 then
			local i = 1
			strResult = strResult .. "[" .. i .. "]" .. GTV(tList[i]) .. ""
		end
		for i = 2, #tList do
			strResult = strResult .. ", [" .. i .. "]" .. GTV(tList[i]) .. ""
		end
		
		strResult = strResult .. "}"
	end
	
	return strResult
end

function Obj2String(obj, bOneLine)
	local strIndentUnit = "   "
	local strLineBreak = "\r\n"
	if bOneLine then
		strIndentUnit = ""
		strLineBreak = ""
	end
	
	local function _Obj2String(obj, strIndent)
		if type(obj) ~= "table" then
			local strNonTable = tostring(obj)
			if type(obj) == "string" then
				strNonTable = "\"" .. strNonTable .. "\""
			end
			return strNonTable
		end
		
		local strNextIndent = strIndent .. strIndentUnit
		local strTable = "{" .. strLineBreak
		for key, value in pairs(obj) do
			strTable = strTable
				.. strNextIndent .. "[" .. _Obj2String(key, strNextIndent) .. "] = " .. _Obj2String(value, strNextIndent) .. ", " .. strLineBreak
		end
		strTable = strTable .. strIndent .. "}"
		
		return strTable
	end
	
	return _Obj2String(obj, "")
end

function DumpObj(obj, strObjName)
	local strIndent = ""
	if type(strObjName) == "string" then
		strIndent = strObjName
	end

	local function _DumpObj(obj, strIndent)
		if type(strIndent) ~= "string" then return end
		
		if type(obj) ~= "table" then
			Log(strIndent .. " obj = " .. GTV(obj))
			return
		end
		
		local strIndentUnit = "   "
		local bEmpty = true
		for key, value in pairs(obj) do
			bEmpty = false
			Log(strIndent .. " obj(" .. tostring(obj) .. ")[" .. GTV(key) .. "] = " .. GTV(value))
			_DumpObj(value, strIndent .. strIndentUnit)
		end
		
		if bEmpty then
			Log(strIndent .. " obj(" .. tostring(obj) .. ") = {}")
		end
	end
	
	_DumpObj(obj, strIndent)
end

function RemoveStringEndingBackslash(strSrc)
	local strResult = nil
	
	if type(strSrc) == "string" then
		strResult = strSrc
		if string.sub(strSrc, string.len(strSrc), -1) == "\\" then
			strResult = string.sub(strSrc, 1, string.len(strSrc) - 1)
		end
	end
	
	return strResult
end


function FileTime2UTCInSec(tFileTime)
	local nUTCInSec = 0
	
	if type(tFileTime) == "table" and type(tFileTime.dwLowDateTime) == "number" and type(tFileTime.dwHighDateTime) == "number" then
		local nYear, nMonth, nDay, nHour, nMin, nSec = tipUtil:FileTime2LocalTime(tFileTime.dwLowDateTime, tFileTime.dwHighDateTime)
		if type(nYear) == "number" and type(nMonth) == "number" and type(nDay) == "number" 
			and type(nHour) == "number" and type(nMin) == "number" and type(nSec) == "number" 
			then
			local nResult = tipUtil:DateTime2Seconds(nYear, nMonth, nDay, nHour, nMin, nSec)
			if type(nResult) == "number" then
				nUTCInSec = nResult
			end
		end
	end
	
	return nUTCInSec
end

function SimpleUrlDecode(strToDecode)
	local strDecoded = nil
	
	if type(strToDecode) == "string" then
		strDecoded = strToDecode
		strDecoded = string.gsub(strDecoded, "+", " ")
		strDecoded = string.gsub(strDecoded, "%%(%x%x)", 
			function (strCapturedByteInHex)
				return string.char(tonumber(strCapturedByteInHex, 16))
			end)
	end
		
	return strDecoded
end

function LoadTableFromFile(strDatFilePath)
	local tResult = nil
	
	if IsRealString(strDatFilePath) and tipUtil:QueryFileExists(strDatFilePath) then
		local tMod = XLLoadModule(strDatFilePath)
		if type(tMod) == "table" and type(tMod.GetSubTable) == "function" then
			local tDat = tMod.GetSubTable()
			if type(tDat) == "table" then
				tResult = tDat
			end
		end
	end
	
	return tResult
end

function SaveTableToFile(tDat, strTargetFilePath)
	local bSuc = false
	
	if type(tDat) == "table" and IsRealString(strTargetFilePath) then
		local strDir = tipUtil:GetFileDir(strTargetFilePath)
		if IsRealString(strDir) then
			if not tipUtil:QueryFileExists(strDir) then
				tipUtil:CreateDir(strDir)
			end
			if tipUtil:QueryFileExists(strDir) then
				tipUtil:SaveLuaTableToLuaFile(tDat, strTargetFilePath)
				bSuc = true
			end
		end
	end
	
	return bSuc
end

function SplitStringBySeperator(strToSplit, strSeperator, bPlain)
	local tResult = {}
	
	if type(bPlain) ~= "boolean" then
		bPlain = false
	end
	
	if type(strToSplit) == "string" and type(strSeperator) == "string" then
		local nSepStartPos = 0
		local nSepEndPos = 0
		local nLastSepStartPos = 0
		local nLastSepEndPos = 0
		while true do
			nLastSepStartPos = nSepStartPos
			nLastSepEndPos = nSepEndPos
			nSepStartPos, nSepEndPos = string.find(strToSplit, strSeperator, nLastSepEndPos + 1, bPlain)
			if type(nSepStartPos) ~= "number" or type(nSepEndPos) ~= "number" then
				tResult[#tResult + 1] = string.sub(strToSplit, nLastSepEndPos + 1, -1)
				break
			end
			tResult[#tResult + 1] = string.sub(strToSplit, nLastSepEndPos + 1, nSepStartPos - 1)
		end
	end

	return tResult
end

function ExtractFileNameFromUrl(strUrl)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[ExtractFileNameFromUrl] " .. tostring(strMsg))
	end
	
	local strFileName = nil
	
	if IsRealString(strUrl) then
		local tPathSet = SplitStringBySeperator(strUrl, "/", true)
		if #tPathSet > 0 then
			local tFileAndParam = SplitStringBySeperator(tPathSet[#tPathSet], "?", true)
			if #tFileAndParam > 0 then
				strFileName = tFileAndParam[1]
			end
		end
	end
	
	Log("strUrl = " .. GTV(strUrl) .. ". strFileName = " .. GTV(strFileName))
	
	return strFileName
end

function CheckFileIntegrity(strFilePath, strExpectedMD5, nExpectedFileSizeInByte)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[CheckFileIntegrity] " .. tostring(strMsg))
	end
	
	local bPassCheck = false
	
	local function CheckFileExists()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[CheckFileExists] " .. tostring(strMsg))
		end
		
		local bPassCheck = false
		
		if IsRealString(strFilePath) and tipUtil:QueryFileExists(strFilePath) then
			bPassCheck = true
		end
		
		Log("bPassCheck = " .. GTV(bPassCheck))
		return bPassCheck
	end
	
	local function MyCheckFileMD5()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[MyCheckFileMD5] " .. tostring(strMsg))
		end
		
		local bPassCheck = false
		
		if type(strExpectedMD5) ~= "string" or type(tipUtil.GetMD5Value) ~= "function" then
			bPassCheck = true --如果待检查项期望值没有指明，或者指明了但本身不支持对该项的检查，则检查通过
		else
			local strMD5 = tipUtil:GetMD5Value(strFilePath)
			if type(strMD5) == "string" and string.upper(strMD5) == string.upper(strExpectedMD5) then
				bPassCheck = true
			end
		end
		
		Log("bPassCheck = " .. GTV(bPassCheck))
		return bPassCheck
	end
	
	local function CheckFileSize()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[CheckFileSize] " .. tostring(strMsg))
		end
		
		local bPassCheck = false
		
		if type(nExpectedFileSizeInByte) ~= "number" or type(tipUtil.GetFileSize) ~= "function" then
			bPassCheck = true --如果待检查项期望值没有指明，或者指明了但本身不支持对该项的检查，则检查通过
		else
			local nFileSizeInByte = tipUtil:GetFileSize(strFilePath)
			if nFileSizeInByte == nExpectedFileSizeInByte then
				bPassCheck = true
			end
		end
		
		Log("bPassCheck = " .. GTV(bPassCheck))
		return bPassCheck
	end
	
	bPassCheck = CheckFileExists() and MyCheckFileMD5() and CheckFileSize()
	
	Log("bPassCheck = " .. GTV(bPassCheck) .. ". strFilePath = " .. GTV(strFilePath) .. ", strExpectedMD5 = " .. GTV(strExpectedMD5) .. ", nExpectedFileSizeInByte = " .. GTV(nExpectedFileSizeInByte) .. "")
	return bPassCheck
end

function GetIExploreExeFilePath()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[GetIExploreExeFilePath] " .. tostring(strMsg))
	end
	
	local strIExploreExeFilePath = nil
	
	local strFilePath = tipUtil:QueryRegValue("HKEY_LOCAL_MACHINE", "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\IEXPLORE.EXE", "")
	if IsRealString(strFilePath) and tipUtil:QueryFileExists(strFilePath) then
		strIExploreExeFilePath = strFilePath
	end
	
	Log("strIExploreExeFilePath = " .. GTV(strIExploreExeFilePath))
	return strIExploreExeFilePath
end

function GetIeMajorVersion()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[GetIeMajorVersion] " .. tostring(strMsg))
	end
	
	local nIeMajorVersion = nil
	
	local strIExploreExeFilePath = GetIExploreExeFilePath()
	if IsRealString(strIExploreExeFilePath) then
		local strIeVersion = tipUtil:GetVersionString(strIExploreExeFilePath)
		Log("strIeVersion = " .. GTV(strIeVersion))
		if IsRealString(strIeVersion) then
			local tSubVersionSet = SplitStringBySeperator(strIeVersion, ".", true)
			Log("tSubVersionSet = " .. List2String(tSubVersionSet))
			if IsNumberString(tSubVersionSet[1]) then
				nIeMajorVersion = tonumber(tSubVersionSet[1])
			end
		end
	end
	
	Log("nIeMajorVersion = " .. GTV(nIeMajorVersion))
	return nIeMajorVersion
end

function IsFileVersionInRange(strFilePath, strMinVersion, strMaxVersion) --nil既可以表示+8，也可以表示-8
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[IsFileVersionInRange] " .. tostring(strMsg))
	end
	
	local bInRange = false
	
	if IsRealString(strFilePath) and tipUtil:QueryFileExists(strFilePath) then
		local strVersion = tipUtil:GetVersionString(strFilePath)
		if IsRealString(strVersion) then
			local function LE(strLeftVersion, strRightVersion)
				local bOk = false
				local bLE = nil
				
				if (not IsRealString(strLeftVersion)) or (not IsRealString(strRightVersion)) then
					bOk = true
					bLE = true
				else
					local bCmpOk, nCmpResult = tipUtil:CompareVersionString(strLeftVersion, strRightVersion)
					if bCmpOk and type(nCmpResult) == "number" then
						bOk = true
						bLE = (nCmpResult == -1 or nCmpResult == 0)
					end
				end
				
				return bOk, bLE
			end
			
			local bOk1, bLE1 = LE(strMinVersion, strVersion)
			local bOk2, bLE2 = LE(strVersion, strMaxVersion)
			
			if bOk1 and bOk2 then
				bInRange = (bLE1 and bLE2)
			end
		end
	end
	
	Log("bInRange = " .. GTV(bInRange) .. ". strFilePath = " .. GTV(strFilePath) .. ", strMinVersion = " .. GTV(strMinVersion) .. ", strMaxVersion = " .. GTV(strMaxVersion))
	return bInRange
end

function CmpMultiDotVersionString(strVersionX, strVersionY)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[CmpMultiDotVersionString] " .. tostring(strMsg))
	end

	local nCmpResult = nil
	
	if IsRealString(strVersionX) and IsRealString(strVersionY) then
		local tSubVersionListX = SplitStringBySeperator(strVersionX, ".", true)
		local tSubVersionListY = SplitStringBySeperator(strVersionY, ".", true)
		
		local function ValidateSubVersionList(tSubVersionList)
			local OldLog = Log
			local function Log(strMsg)
				OldLog("[ValidateSubVersionList] " .. tostring(strMsg))
			end

			local bValid = false
			
			if type(tSubVersionList) == "table" and #tSubVersionList >= 1 then
				bValid = true
				
				for i = 1, #tSubVersionList do
					if not IsNumberString(tSubVersionList[i]) then
						bValid = false
						break
					end
				end
			end
			
			Log("bValid = " .. GTV(bValid))
			return bValid
		end
		
		if ValidateSubVersionList(tSubVersionListX) 
			and ValidateSubVersionList(tSubVersionListY) 
			and #tSubVersionListX == #tSubVersionListY 
			then
			
			local function CmpSubVersionList(tSubVersionListX, tSubVersionListY)
				local nCmpResult = nil
				
				for i = 1, #tSubVersionListX do
					if type(nCmpResult) ~= "number" then
						local nSubVersionX = tonumber(tSubVersionListX[i])
						local nSubVersionY = tonumber(tSubVersionListY[i])
						if nSubVersionX > nSubVersionY then
							nCmpResult = 1
						elseif nSubVersionX < nSubVersionY then
							nCmpResult = -1
						elseif i == #tSubVersionListX then
							nCmpResult = 0
						end
					end
				end
				
				return nCmpResult
			end
			
			local function CmpSubVersionListV2(tSubVersionListX, tSubVersionListY)
				local function CmpSubVersion(nSubVersionX, nSubVersionY)
					local nCmpResult = nil
					
					if nSubVersionX > nSubVersionY then
						nCmpResult = 1
					elseif nSubVersionX < nSubVersionY then
						nCmpResult = -1
					else
						nCmpResult = 0
					end
					
					return nCmpResult
				end
				
				local function CnC(i)
					local nSubVersionX = tonumber(tSubVersionListX[i])
					local nSubVersionY = tonumber(tSubVersionListY[i])
					local nCmpResult = CmpSubVersion(nSubVersionX, nSubVersionY)
					if nCmpResult ~= 0 or i == #tSubVersionListX then
						return nCmpResult
					end
					
					return CnC(i + 1)
				end
				
				return CnC(1)
			end
			
			local function CmpSubVersionListV3(tSubVersionListX, tSubVersionListY)
				local function CmpSubVersion(nSubVersionX, nSubVersionY)
					local nCmpResult = nil
					
					if nSubVersionX > nSubVersionY then
						nCmpResult = 1
					elseif nSubVersionX < nSubVersionY then
						nCmpResult = -1
					else
						nCmpResult = 0
					end
					
					return nCmpResult
				end
				
				local nSubVersionX = tonumber(tSubVersionListX[1])
				local nSubVersionY = tonumber(tSubVersionListY[1])
				local nCmpResult = CmpSubVersion(nSubVersionX, nSubVersionY)
				if nCmpResult ~= 0 or 1 == #tSubVersionListX then
					return nCmpResult
				end
				
				return CmpSubVersionListV3(SpliceList(tSubVersionListX, 2, -1), SpliceList(tSubVersionListY, 2, -1))
			end
			
			--nCmpResult = CmpSubVersionList(tSubVersionListX, tSubVersionListY)
			--nCmpResult = CmpSubVersionListV2(tSubVersionListX, tSubVersionListY)
			nCmpResult = CmpSubVersionListV3(tSubVersionListX, tSubVersionListY)
		end
	end
	
	Log("nCmpResult = " .. GTV(nCmpResult))
	return nCmpResult
end

function GetTableKeyList(tInput)
	local tKeyList = nil
	
	if type(tInput) == "table" then
		tKeyList = {}
		for key, value in pairs(tInput) do
			tKeyList[#tKeyList + 1] = key
		end
	end
	
	return tKeyList
end

function FindFirstInList(tList, fCheck)
	local bFound, nFirstItemIdx = false, nil
	
	if type(tList) == "table" and type(fCheck) == "function" then
		for i = 1, #tList do
			if fCheck(tList[i]) then
				bFound = true
				nFirstItemIdx = i
				break
			end
		end
	end
	
	return bFound, nFirstItemIdx
end

function FindLastInList(tList, fCheck)
	local bFound, nLastItemIdx = false, nil
	
	if type(tList) == "table" and type(fCheck) == "function" then
		for i = #tList, 1, -1 do
			if fCheck(tList[i]) then
				bFound = true
				nLastItemIdx = i
				break
			end
		end
	end
	
	return bFound, nLastItemIdx
end

function SpliceList(tList, nMinIdx, nMaxIdx)
	local tSubList = nil
	
	if type(tList) == "table" and type(nMinIdx) == "number" and type(nMaxIdx) == "number" then
		tSubList = {}
		
		local function FixIdx(nIdx)
			local nNewIdx = nIdx
			
			if nNewIdx >= 0 then
				if nNewIdx > #tList then
					nNewIdx = #tList
				end
			else
				if (0 - nNewIdx) > #tList then
					nNewIdx = 0 - #tList
				end
				
				nNewIdx = #tList + nNewIdx + 1
			end
			
			return nNewIdx
		end
		
		local nNewMinIdx = FixIdx(nMinIdx)
		local nNewMaxIdx = FixIdx(nMaxIdx)
		for i = nNewMinIdx, nNewMaxIdx do
			tSubList[#tSubList + 1] = tList[i]
		end
	end
	
	return tSubList
end

function GrepList(tList, fCheck)
	local tRightSubList = nil
	local tWrongSubList = nil
	
	if type(tList) == "table" and type(fCheck) == "function" then
		tRightSubList = {}
		tWrongSubList = {}
		
		for i = 1, #tList do
			if fCheck(tList[i]) then
				tRightSubList[#tRightSubList + 1] = tList[i]
			else
				tWrongSubList[#tWrongSubList + 1] = tList[i]
			end
		end
	end
	
	return tRightSubList, tWrongSubList
end

function SortList(tList, fCmp) --最大的元素索引为1
	local tSortedList = nil
	
	if type(tList) == "table" and type(fCmp) == "function" then
		local nListLen = #tList
		
		--拷贝该list
		tSortedList = {}
		for i = 1, nListLen do
			tSortedList[i] = tList[i]
		end
		
		for i = nListLen, 1, -1 do --冒泡排序
			for j = 1, i - 1 do
				if fCmp(tSortedList[j], tSortedList[j + 1]) < 0 then
					tSortedList[j], tSortedList[j + 1] = tSortedList[j + 1], tSortedList[j] --交换j和紧跟在它后面的那个元素
				end
			end
			--assert: 第i小的元素放在了索引为i的位置上
		end
	end
	
	return tSortedList
end

function Foreach(tList, fVisit)
	if type(tList) == "table" and type(fVisit) == "function" then
		for i = 1, #tList do
			local item = tList[i]
			fVisit(item)
		end
	end
end

function ForeachPair(tInput, fVisit)
	if type(tInput) == "table" and type(fVisit) == "function" then
		for key, value in pairs(tInput) do
			fVisit(key, value)
		end
	end
end

--******************* framework **************************

function SowRandomSeed()
	local nCurrentUTCInSeconds = tipUtil:GetCurrentUTCTime()
	math.randomseed(tonumber(tostring(nCurrentUTCInSeconds):reverse():sub(1,6)))
end

function PreEntry()
	SowRandomSeed()
	
	--TODO:
end

function PostExit()
	--TODO:
end

local nFlowNum = 0
function Inc(strReason)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[Inc] " .. tostring(strMsg))
	end
	
	if nFlowNum == 0 then
		PreEntry()
	end
	local nLastFlowNum = nFlowNum
	nFlowNum = nFlowNum + 1
	Log("nFlowNum " .. tostring(nLastFlowNum) .. " -> " .. tostring(nFlowNum) .. " because of " .. tostring(strReason))
end

function Dec(strReason)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[Dec] " .. tostring(strMsg))
	end
	
	local nLastFlowNum = nFlowNum
	nFlowNum = nFlowNum - 1
	Log("nFlowNum " .. tostring(nLastFlowNum) .. " -> " .. tostring(nFlowNum) .. " because of " .. tostring(strReason))
	if nFlowNum <= 0 then
		PostExit()
		Log("SoftExit")
		--tipAsynUtil:SoftExit()
	end
end

function GetHttpFile(strUrl, strFilePath, fCallback)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[GetHttpFile] " .. tostring(strMsg))
	end
	
	Inc("GetHttpFile(" .. GTV(strUrl) .. ", " .. GTV(strFilePath) .. ", " .. GTV(fCallback) .. ")")
	local nGuardTimerId = nil
	local bEntry = false
	local function CallbackWrapper(strTargetFilePath)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[CallbackWrapper] " .. tostring(strMsg))
		end
		
		if type(nGuardTimerId) == "number" then
			tipAsynUtil:KillTimer(nGuardTimerId)
			nGuardTimerId = nil
		end
		if not bEntry then
			bEntry = true
			fCallback(strTargetFilePath)
			Dec("GetHttpFile(" .. GTV(strUrl) .. ", " .. GTV(strFilePath) .. ", " .. GTV(fCallback) .. ")")
		end
	end
	
	local function ValidateArg_strUrl()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[ValidateArg_strUrl] " .. tostring(strMsg))
		end
		
		local bValid = false
		
		if IsRealString(strUrl) then
			bValid = true
		end
		
		Log("bValid = " .. GTV(bValid))
		return bValid
	end
	
	local function ValidateArg_strFilePath()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[ValidateArg_strFilePath] " .. tostring(strMsg))
		end
		
		local bValid = false
		
		if IsRealString(strFilePath) then
			bValid = true
		end
		
		Log("bValid = " .. GTV(bValid))
		return bValid
	end
	
	if (not ValidateArg_strUrl()) or (not ValidateArg_strFilePath()) then
		CallbackWrapper()
		return
	end
	
	tipAsynUtil:AjaxGetHttpFile(strUrl, strFilePath, 
		function (nRet, strTargetFilePath)
			Log("AjaxGetHttpFile nRet = " .. GTV(nRet) 
				.. ", strTargetFilePath = " .. GTV(strTargetFilePath) 
				.. ". strUrl = " .. GTV(strUrl) 
				.. ", strFilePath = " .. GTV(strFilePath) 
				.. "")
			local bOk = false
			
			if 0 == nRet and IsRealString(strTargetFilePath) and tipUtil:QueryFileExists(strTargetFilePath) then
				bOk = true
			end
			
			if not bOk then
				CallbackWrapper()
				return
			end
			
			CallbackWrapper(strTargetFilePath)
		end)
	
	nGuardTimerId = tipAsynUtil:SetTimer(1000 * 30, 
		function (nTimerId)
			Log("time out")
			CallbackWrapper()
		end)
end

function GetHttpContent(strUrl, fCallback)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[GetHttpContent] " .. tostring(strMsg))
	end
	
	Inc("GetHttpContent(" .. GTV(strUrl) .. ", " .. GTV(fCallback) .. ")")
	local nGuardTimerId = nil
	local bEntry = false
	local function CallbackWrapper(strContent)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[CallbackWrapper] " .. tostring(strMsg))
		end
		
		if type(nGuardTimerId) == "number" then
			tipAsynUtil:KillTimer(nGuardTimerId)
			nGuardTimerId = nil
		end
		if not bEntry then
			bEntry = true
			fCallback(strContent)
			Dec("GetHttpContent(" .. GTV(strUrl) .. ", " .. GTV(fCallback) .. ")")
		end
	end
	
	local function ValidateArg_strUrl()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[ValidateArg_strUrl] " .. tostring(strMsg))
		end
		
		local bValid = false
		
		if IsRealString(strUrl) then
			bValid = true
		end
		
		Log("bValid = " .. GTV(bValid))
		return bValid
	end
	
	if not ValidateArg_strUrl() then
		CallbackWrapper()
		return
	end
	
	tipAsynUtil:AjaxGetHttpContent(strUrl, 
		function (nRet, strContent)
			Log("AjaxGetHttpContent nRet = " .. GTV(nRet) 
				.. ", strContent = " .. GTV(strContent) 
				.. ". strUrl = " .. GTV(strUrl) 
				.. "")
			local bOk = false
			
			if 0 == nRet and type(strContent) == "string" then
				bOk = true
			end
			
			if not bOk then
				CallbackWrapper()
				return
			end
			
			CallbackWrapper(strContent)
		end)
	
	nGuardTimerId = tipAsynUtil:SetTimer(1000 * 30, 
		function (nTimerId)
			Log("time out")
			CallbackWrapper()
		end)
end

function SendHttpStat(strUrl, fCallback)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[SendHttpStat] " .. tostring(strMsg))
	end
	
	Inc("SendHttpStat(" .. GTV(strUrl) .. ", " .. GTV(fCallback) .. ")")
	local nGuardTimerId = nil
	local bEntry = false
	local function CallbackWrapper()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[CallbackWrapper] " .. tostring(strMsg))
		end
		
		if type(nGuardTimerId) == "number" then
			tipAsynUtil:KillTimer(nGuardTimerId)
			nGuardTimerId = nil
		end
		if not bEntry then
			bEntry = true
			if type(fCallback) == "function" then
				fCallback()
			end
			Dec("SendHttpStat(" .. GTV(strUrl) .. ", " .. GTV(fCallback) .. ")")
		end
	end
	
	local function ValidateArg_strUrl()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[ValidateArg_strUrl] " .. tostring(strMsg))
		end
		
		local bValid = false
		
		if IsRealString(strUrl) then
			bValid = true
		end
		
		Log("bValid = " .. GTV(bValid))
		return bValid
	end
	
	if not ValidateArg_strUrl() then
		CallbackWrapper()
		return
	end
	
	tipAsynUtil:AjaxSendHttpStat(strUrl, 
		function (nRet)
			Log("AjaxSendHttpStat nRet = " .. GTV(nRet) 
				.. ". strUrl = " .. GTV(strUrl) 
				.. "")
			
			CallbackWrapper()
		end)
	
	nGuardTimerId = tipAsynUtil:SetTimer(1000 * 30, 
		function (nTimerId)
			Log("time out")
			
			CallbackWrapper()
		end)
end

function UnzipToDir(strZipFilePath, strDir, fCallback)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[UnzipToDir] " .. tostring(strMsg))
	end
	
--[[
	UnzipToDir("c:\\TSLOG\\XAppPush\\xx.xyz.65389.lua.zip", "c:\\TSLOG\\XAppPush\\temp\\", 
		function (nRet)
			Log("nRet = " .. GTV(nRet))
		end)
	UnzipToDir("c:\\TSLOG\\XAppPush\\xx.xyz.65389.lua.zip", "c:\\TSLOG\\XAppPush\\temp2", 
		function (nRet)
			Log("nRet = " .. GTV(nRet))
		end)
	UnzipToDir("c:\\TSLOG\\XAppPush\\38534.lua.zip", "c:\\TSLOG\\XAppPush\\temp2", 
		function (nRet)
			Log("nRet = " .. GTV(nRet))
		end)
--]]
	Inc("UnzipToDir(" .. GTV(strZipFilePath) .. ", " .. GTV(strDir) .. ", " .. GTV(fCallback) .. ")")
	local function CallbackWrapper(nRet)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[CallbackWrapper] " .. tostring(strMsg))
		end
		
		fCallback(nRet)
		Dec("UnzipToDir(" .. GTV(strZipFilePath) .. ", " .. GTV(strDir) .. ", " .. GTV(fCallback) .. ")")
	end
	
	local function ValidateArg_strZipFilePath()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[ValidateArg_strZipFilePath] " .. tostring(strMsg))
		end
		
		local bValid = false
		
		if IsRealString(strZipFilePath) and tipUtil:QueryFileExists(strZipFilePath) then
			bValid = true
		end
		
		Log("bValid = " .. GTV(bValid))
		return bValid
	end
	
	local function ValidateArg_strDir()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[ValidateArg_strDir] " .. tostring(strMsg))
		end
		
		local bValid = false
		
		if IsRealString(strDir) then
			if not tipUtil:QueryFileExists(strDir) then
				tipUtil:CreateDir(strDir)
			end
			if tipUtil:QueryFileExists(strDir) then
				bValid = true
			end
		end
		
		Log("bValid = " .. GTV(bValid))
		return bValid
	end
	
	if (not ValidateArg_strZipFilePath()) or (not ValidateArg_strDir()) then
		CallbackWrapper(-2)
		return
	end
	
	tipAsynUtil:AsynMiniUnzip(strZipFilePath, strDir, 
		function (nRet)
			Log("AsynMiniUnzip nRet = " .. GTV(nRet) 
				.. ". strZipFilePath = " .. GTV(strZipFilePath) 
				.. ", strDir = " .. GTV(strDir) 
				.. "")
			CallbackWrapper(nRet)
		end)
end

function NewKillProcess(nProcessId, fCallback, bSucIfProcNotExist)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[NewKillProcess] " .. tostring(strMsg))
	end
	
	Inc("NewKillProcess(nProcessId = " .. GTV(nProcessId) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfProcNotExist = " .. GTV(bSucIfProcNotExist) 
		.. ")")
	Log("nProcessId = " .. GTV(nProcessId) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfProcNotExist = " .. GTV(bSucIfProcNotExist) 
		.. "")
	local bSuc = false
	local nRetryTimerId = nil
	local bEntry = false --保证回调只调一次，引用计数只减一次
	local function CallbackWrapper()
		if type(nRetryTimerId) == "number" then --如果重试定时器曾经启动过，则关闭它且仅关闭一次
			tipAsynUtil:KillTimer(nRetryTimerId)
			nRetryTimerId = nil
		end
		if not bEntry then
			bEntry = true
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
			
			Dec("NewKillProcess(nProcessId = " .. GTV(nProcessId) 
				.. ", fCallback = " .. GTV(fCallback) 
				.. ", bSucIfProcNotExist = " .. GTV(bSucIfProcNotExist) 
				.. ")") --先回调再减引用计数否则可能因为引用计数已经减至0使回调得不到执行
		end
	end
	
	if type(nProcessId) ~= "number" then
		CallbackWrapper()
		return
	end
	
	local strCmdLine, strImageFilePath = tipUtil:GetProcessCmdLine(nProcessId)
	Log("strCmdLine = " .. GTV(strCmdLine) 
		.. ", strImageFilePath = " .. GTV(strImageFilePath) 
		.. "")
	if not IsRealString(strImageFilePath) then
		if bSucIfProcNotExist then
			bSuc = true
		end
		CallbackWrapper()
		return
	end
	
	local bOk = tipUtil:KillProcess(nProcessId, 0) --第一次杀，如果杀成功，则直接返回
	if bOk then
		bSuc = true
		CallbackWrapper()
		return
	end
	
	--第一次杀不成功，最多重试三次，每次重试距离上次杀间隔1s
	local nRetryCount = 3
	local nRetryIntervalInMS = 1000 * 1
	nRetryTimerId = tipAsynUtil:SetTimer(nRetryIntervalInMS, 
		function (nTimerId)
			Log("time out. nRetryCount = " .. GTV(nRetryCount) .. "")
			
			if nRetryCount <= 0 then --重试次数用光，则认为杀失败，直接返回
				CallbackWrapper()
				return
			end
			
			--重试次数还剩，再杀一次，如果杀成功，则直接返回，否则等下次重试
			local bOk = tipUtil:KillProcess(nProcessId, 0)
			if bOk then
				bSuc = true
				CallbackWrapper()
				return
			end
			
			nRetryCount = nRetryCount - 1 --此次重试机会已用，可用重试计数减一
		end)
end

function SetOnceTimer(nTimerSpanInMs, fCallback)
	Inc("SetOnceTimer(" .. GTV(nTimerSpanInMs) .. ", " .. GTV(fCallback) .. ")")
	local nTimerId = nil
	local function CallbackWrapper()
		if type(nTimerId) == "number" then
			tipAsynUtil:KillTimer(nTimerId)
			nTimerId = nil
		end
		
		fCallback()
		Dec("SetOnceTimer(" .. GTV(nTimerSpanInMs) .. ", " .. GTV(fCallback) .. ")")
	end
	
	nTimerId = tipAsynUtil:SetTimer(nTimerSpanInMs, 
		function (nTimerId)
			CallbackWrapper()
		end)
end

function CreateProcess(strAppName, strCmdLine, strWorkingDir, nPriority, nShow, fCallback)
	Inc("CreateProcess(strAppName = " .. GTV(strAppName) 
		.. ", strCmdLine = " .. GTV(strCmdLine) 
		.. ", strWorkingDir = " .. GTV(strWorkingDir) 
		.. ", nPriority = " .. GTV(nPriority) 
		.. ", nShow = " .. GTV(nShow) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ")")
	local nPID = nil
	local function CallbackWrapper()
		fCallback(nPID)
		Dec("CreateProcess(strAppName = " .. GTV(strAppName) 
		.. ", strCmdLine = " .. GTV(strCmdLine) 
		.. ", strWorkingDir = " .. GTV(strWorkingDir) 
		.. ", nPriority = " .. GTV(nPriority) 
		.. ", nShow = " .. GTV(nShow) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ")")
	end
	
	tipAsynUtil:AsynCreateProcess(strAppName, strCmdLine, strWorkingDir, nPriority, nShow, 
		function (nRet, tProcDetail)
			if 0 == nRet and type(tProcDetail) == "table" and type(tProcDetail["dwProcessId"]) == "number" then
				nPID = tProcDetail["dwProcessId"]
			end
			
			CallbackWrapper()
		end)
end

--************************************************

function IsXmpInstalled()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[IsXmpInstalled] " .. tostring(strMsg))
	end
	
	local bInstalled, strXmpExeFilePath, strXmpExeFileVersion, strMiniXmpExeFilePath, strMiniXmpExeFileVersion = false, nil, nil, nil, nil
	
	local function IsBigXmpInstalled()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[IsBigXmpInstalled] " .. tostring(strMsg))
		end
		
		local bBigXmpInstalled, strBigXmpExeFilePath, strBigXmpExeFileVersion = false, nil, nil
		
		local strRoot = "HKEY_LOCAL_MACHINE"
		local strDir = "SOFTWARE\\Thunder Network\\Xmp"
		if tipUtil:QueryRegKeyExists(strRoot, strDir) then
			local strFilePath = tipUtil:QueryRegValue(strRoot, strDir, "Path")
			if IsRealString(strFilePath) and tipUtil:QueryFileExists(strFilePath) then
				local strVersion = tipUtil:GetVersionString(strFilePath)
				if IsRealString(strVersion) then
					bBigXmpInstalled = true
					strBigXmpExeFilePath = strFilePath
					strBigXmpExeFileVersion = strVersion
				end
			end
		end
		
		Log("bBigXmpInstalled = " .. GTV(bBigXmpInstalled) 
			.. ", strBigXmpExeFilePath = " .. GTV(strBigXmpExeFilePath) 
			.. ", strBigXmpExeFileVersion = " .. GTV(strBigXmpExeFileVersion) 
			.. "")
		return bBigXmpInstalled, strBigXmpExeFilePath, strBigXmpExeFileVersion
	end
	
	local function IsSmallXmpInstalled()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[IsSmallXmpInstalled] " .. tostring(strMsg))
		end
		
		local bSmallXmpInstalled, strSmallXmpExeFilePath, strSmallXmpExeFileVersion = false, nil, nil
		
		local strXmp4Dir = GetXmp4Dir()
		if IsRealString(strXmp4Dir) then
			local strFilePath = tipUtil:PathCombine(strXmp4Dir, "Core\\Program\\xmp.exe")
			if IsRealString(strFilePath) and tipUtil:QueryFileExists(strFilePath) then
				local strVersion = tipUtil:GetVersionString(strFilePath)
				if IsRealString(strVersion) then
					bSmallXmpInstalled = true
					strSmallXmpExeFilePath = strFilePath
					strSmallXmpExeFileVersion = strVersion
				end
			end
		end
		
		Log("bSmallXmpInstalled = " .. GTV(bSmallXmpInstalled) 
			.. ", strSmallXmpExeFilePath = " .. GTV(strSmallXmpExeFilePath) 
			.. ", strSmallXmpExeFileVersion = " .. GTV(strSmallXmpExeFileVersion) 
			.. "")
		return bSmallXmpInstalled, strSmallXmpExeFilePath, strSmallXmpExeFileVersion
	end
	
	local bBigXmpInstalled, strBigXmpExeFilePath, strBigXmpExeFileVersion = IsBigXmpInstalled()
	local bSmallXmpInstalled, strSmallXmpExeFilePath, strSmallXmpExeFileVersion = IsSmallXmpInstalled()
	if bBigXmpInstalled and bSmallXmpInstalled then
		bInstalled = true
		strXmpExeFilePath = strBigXmpExeFilePath
		strXmpExeFileVersion = strBigXmpExeFileVersion
		strMiniXmpExeFilePath = strSmallXmpExeFilePath
		strMiniXmpExeFileVersion = strSmallXmpExeFileVersion
	end
	
	Log("bInstalled = " .. GTV(bInstalled) 
		.. ", strXmpExeFilePath = " .. GTV(strXmpExeFilePath) 
		.. ", strXmpExeFileVersion = " .. GTV(strXmpExeFileVersion) 
		.. ", strMiniXmpExeFilePath = " .. GTV(strMiniXmpExeFilePath) 
		.. ", strMiniXmpExeFileVersion = " .. GTV(strMiniXmpExeFileVersion) 
		.. "")
	return bInstalled, strXmpExeFilePath, strXmpExeFileVersion, strMiniXmpExeFilePath, strMiniXmpExeFileVersion
end

function SetXmpAssociatedFileExt(tFileExtList)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[SetXmpAssociatedFileExt] " .. tostring(strMsg))
	end
	
	Log("tFileExtList = " .. List2String(tFileExtList))
	
	local bSuc = false
	
	if type(tFileExtList) == "table" then
		
		local function FormatFileExtList()
			local OldLog = Log
			local function Log(strMsg)
				OldLog("[FormatFileExtList] " .. tostring(strMsg))
			end
			
			local strResult = ""
			
			Foreach(tFileExtList, 
				function (strFileExt)
					if IsRealString(strFileExt) then
						local strItem = "." .. strFileExt .. ";"
						strResult = strResult .. strItem
					end
				end)
			Log("strResult = " .. GTV(strResult))
			return strResult
		end
		
		local function SetAssociation(strAssociation)
			local OldLog = Log
			local function Log(strMsg)
				OldLog("[SetAssociation] " .. tostring(strMsg))
			end
			
			Log("strAssociation = " .. GTV(strAssociation))
			
			local bSuc = false
			
			local strRoot = "HKEY_CURRENT_USER"
			local strDir = "Software\\Thunder Network\\Xmp"
			if tipUtil:QueryRegKeyExists(strRoot, strDir) then
				local strValName = "AssociationFileExt"
				local strOldAssociation = tipUtil:QueryRegValue(strRoot, strDir, strValName)
				if (not IsRealString(strOldAssociation)) 
					or (string.lower(strOldAssociation) ~= string.lower(strAssociation)) 
					then
					local bOk = tipUtil:SetRegValue(strRoot, strDir, strValName, strAssociation)
					if bOk then
						strOldAssociation = tipUtil:QueryRegValue(strRoot, strDir, strValName)
					end
				end
				if IsRealString(strOldAssociation) and string.lower(strOldAssociation) == string.lower(strAssociation) then
					bSuc = true
				end
			end
			Log("bSuc = " .. GTV(bSuc))
			return bSuc
		end
		
		if IsXmpInstalled() then
			local strAssociation = FormatFileExtList()
			if IsRealString(strAssociation) then
				bSuc = SetAssociation(strAssociation)
			end
		end
	end
	
	Log("bSuc = " .. GTV(bSuc))
	return bSuc
end

function GetXmp4Dir()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[GetXmp4Dir] " .. tostring(strMsg))
	end
	
	local strXmp4Dir = nil
	
	local strBaseDir = nil
	local nOSMajorVer, nOSMinorVer = tipUtil:GetOSVersion()
	if nOSMajorVer >= 6 then
		--Vista/Win7 or above
		local strToExpand = "%PUBLIC%"
		strBaseDir = tipUtil:ExpandEnvironmentStrings(strToExpand)
		if (not IsRealString(strBaseDir)) 
			or (string.lower(strBaseDir) == string.lower(strToExpand)) 
			then
			strBaseDir = nil
		end
	else
		--XP or below
		local nCSIDL_COMMON_APPDATA = 0x0023
		strBaseDir = tipUtil:GetSpecialFolderPathEx(nCSIDL_COMMON_APPDATA)
	end
	
	if IsRealString(strBaseDir) then
		local strDir = tipUtil:PathCombine(strBaseDir, "Thunder Network\\XMP4")
		if IsRealString(strDir) then
			strXmp4Dir = strDir
		end
	end
	
	Log("strXmp4Dir = " .. GTV(strXmp4Dir))
	return strXmp4Dir
end

function AdjustXmpUserCfg(strCfgItemName, objCfgItemValue)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[AdjustXmpUserCfg] " .. tostring(strMsg))
	end
	
	Log("strCfgItemName = " .. GTV(strCfgItemName) 
		.. ", objCfgItemValue = " .. GTV(objCfgItemValue) 
		.. "")
	local bSuc = false
	
	if IsRealString(strCfgItemName) then
		local strXmp4Dir = GetXmp4Dir()
		if IsRealString(strXmp4Dir) then
			local strXmpUserCfgFilePath = tipUtil:PathCombine(strXmp4Dir, "ProgramData\\UserConfig\\XmpUserConfig.lua")
			local tXmpUserCfg = LoadTableFromFile(strXmpUserCfgFilePath)
			if type(tXmpUserCfg) == "table" then
				local tCfgItem = {}
				tCfgItem["VALUE"] = objCfgItemValue
				tXmpUserCfg[strCfgItemName] = tCfgItem
				if SaveTableToFile(tXmpUserCfg, strXmpUserCfgFilePath) then
					bSuc = true
				end
			end
		end
	end
	
	Log("bSuc = " .. GTV(bSuc))
	return bSuc
end
	
function CheckXmpAliveByWnd()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[CheckXmpAliveByWnd] " .. tostring(strMsg))
	end
	
	local bXmpAlive, uWndHandle = false, nil
	
	uWndHandle = tipUtil:FindWindow("XmpPlayer", nil)
	if uWndHandle then
		bXmpAlive = true
	end
	
	Log("bXmpAlive = " .. GTV(bXmpAlive) 
		.. ", uWndHandle = " .. GTV(uWndHandle) 
		.. "")
	return bXmpAlive, uWndHandle
end

function ShutdownXmp(nTimeoutInMS, fCallback, bSucIfXmpNotExist)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[ShutdownXmp] " .. tostring(strMsg))
	end
	
	Inc("ShutdownXmp(nTimeoutInMS = " .. GTV(nTimeoutInMS) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfXmpNotExist = " .. GTV(bSucIfXmpNotExist) 
		.. ")")
	Log("nTimeoutInMS = " .. GTV(nTimeoutInMS) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfXmpNotExist = " .. GTV(bSucIfXmpNotExist) 
		.. "")
	local bSuc = false
	local function CallbackWrapper()
		Log("bSuc = " .. GTV(bSuc))
		fCallback(bSuc)
		
		Dec("ShutdownXmp(nTimeoutInMS = " .. GTV(nTimeoutInMS) 
			.. ", fCallback = " .. GTV(fCallback) 
			.. ", bSucIfXmpNotExist = " .. GTV(bSucIfXmpNotExist) 
			.. ")")
	end
	
	if type(nTimeoutInMS) ~= "number" or nTimeoutInMS < 0 then
		CallbackWrapper()
		return
	end
	
	local bXmpAlive, uWndHandle = CheckXmpAliveByWnd()
	if not bXmpAlive then
		if bSucIfXmpNotExist then
			bSuc = true
		end
		CallbackWrapper()
		return
	end
	
	local nWM_QUERYENDSESSION = 0x0011
	local nSMTO_ABORTIFHUNG = 0x0002
	local SMTO_BLOCK = 0x0001
	tipAsynUtil:SendMessageTimeout(uWndHandle, nWM_QUERYENDSESSION, 0, 0x000000001, 0x0003, nTimeoutInMS, 
		function (nRet)
			if 0 ~= nRet then
				CallbackWrapper()
				return
			end
			
			SetOnceTimer(1000 * 30, --继续等30s，让xmp从容退出
				function ()
					if not CheckXmpAliveByWnd() then
						bSuc = true
					end
					
					CallbackWrapper()
				end)
		end)
end
	
function CheckMiniXmpAliveByWnd()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[CheckMiniXmpAliveByWnd] " .. tostring(strMsg))
	end
	
	local bMiniXmpAlive, uWndHandle = false, nil
	
	uWndHandle = tipUtil:FindWindow("XMP_CMD_HELPER{31317078-A84A-425a-A01C-6071F056D50F}", "迅雷看看播放器")
	if uWndHandle then
		bMiniXmpAlive = true
	end
	
	Log("bMiniXmpAlive = " .. GTV(bMiniXmpAlive) 
		.. ", uWndHandle = " .. GTV(uWndHandle) 
		.. "")
	return bMiniXmpAlive, uWndHandle
end

function ShutdownMiniXmp(nTimeoutInMS, fCallback, bSucIfMiniXmpNotExist)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[ShutdownMiniXmp] " .. tostring(strMsg))
	end
	
	Inc("ShutdownMiniXmp(nTimeoutInMS = " .. GTV(nTimeoutInMS) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfMiniXmpNotExist = " .. GTV(bSucIfMiniXmpNotExist) 
		.. ")")
	Log("nTimeoutInMS = " .. GTV(nTimeoutInMS) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfMiniXmpNotExist = " .. GTV(bSucIfMiniXmpNotExist) 
		.. "")
	local bSuc = false
	local function CallbackWrapper()
		Log("bSuc = " .. GTV(bSuc))
		fCallback(bSuc)
		
		Dec("ShutdownMiniXmp(nTimeoutInMS = " .. GTV(nTimeoutInMS) 
			.. ", fCallback = " .. GTV(fCallback) 
			.. ", bSucIfMiniXmpNotExist = " .. GTV(bSucIfMiniXmpNotExist) 
			.. ")")
	end
	
	if type(nTimeoutInMS) ~= "number" or nTimeoutInMS < 0 then
		CallbackWrapper()
		return
	end
	
	local bMiniXmpAlive, uWndHandle = CheckMiniXmpAliveByWnd()
	if not bMiniXmpAlive then
		if bSucIfMiniXmpNotExist then
			bSuc = true
		end
		CallbackWrapper()
		return
	end
	
	local nWM_QUERYENDSESSION = 0x0011
	local nSMTO_ABORTIFHUNG = 0x0002
	local SMTO_BLOCK = 0x0001
	tipAsynUtil:SendMessageTimeout(uWndHandle, nWM_QUERYENDSESSION, 0, 0x000000001, 0x0003, nTimeoutInMS, 
		function (nRet)
			if 0 ~= nRet then
				CallbackWrapper()
				return
			end
			
			SetOnceTimer(1000 * 30, --继续等30s，让MiniXmp从容退出
				function ()
					if not CheckMiniXmpAliveByWnd() then
						bSuc = true
					end
					
					CallbackWrapper()
				end)
		end)
end

function IsXmpPlaying()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[IsXmpPlaying] " .. tostring(strMsg))
	end
	
	local bXmpPlaying = false
	
	local strClassName = "XMP_CMD_HELPER{31317078-984A-425a-A01C-6071F056D50F}"
	local uWndHandle = tipUtil:FindWindow(strClassName, nil)
	if uWndHandle then
		local strTitle = tipUtil:GetWindowTitle(uWndHandle)
		if IsRealString(strTitle) 
			and string.find(strTitle, "/iduration \"(%d+)\"") 
			then
			bXmpPlaying = true
		end
	end
	
	Log("bXmpPlaying = " .. GTV(bXmpPlaying))
	return bXmpPlaying
end

--*************************************

function IsBaofengInstalled()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[IsBaofengInstalled] " .. tostring(strMsg))
	end
	
	local bInstalled, strBaofengExeFilePath, strBaofengExeFileVersion = false, nil, nil
	
	local strRoot = "HKEY_CURRENT_USER"
	local strDir = "Software\\Baofeng\\AxPlayer"
	if tipUtil:QueryRegKeyExists(strRoot, strDir) then
		local strFilePath = tipUtil:QueryRegValue(strRoot, strDir, "Path")
		if IsRealString(strFilePath) and tipUtil:QueryFileExists(strFilePath) then
			strFilePath = tipUtil:PathCombine(strFilePath, "StormPlayer.exe")
			if IsRealString(strFilePath) and tipUtil:QueryFileExists(strFilePath) then
				local strVersion = tipUtil:GetVersionString(strFilePath)
				if IsRealString(strVersion) then
					bInstalled = true
					strBaofengExeFilePath = strFilePath
					strBaofengExeFileVersion = strVersion
				end
			end
		end
	end
	
	Log("bInstalled = " .. GTV(bInstalled) 
		.. ", strBaofengExeFilePath = " .. GTV(strBaofengExeFilePath) 
		.. ", strBaofengExeFileVersion = " .. GTV(strBaofengExeFileVersion) 
		.. "")
	return bInstalled, strBaofengExeFilePath, strBaofengExeFileVersion
end

function SetBaofengAssociatedFileExt(tFileExtList)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[SetBaofengAssociatedFileExt] " .. tostring(strMsg))
	end
	
	Log("tFileExtList = " .. List2String(tFileExtList))
	
	local bSuc = false
	
	if type(tFileExtList) == "table" then
		--TODO: HKEY_CURRENT_USER\Software\Baofeng\StormPlayer\UserConfig\FileAssociate
		local strRoot = "HKEY_CURRENT_USER"
		local strDir = "Software\\Baofeng\\StormPlayer\\UserConfig\\FileAssociate"
		if tipUtil:QueryRegKeyExists(strRoot, strDir) 
			and not FindFirstInList(tFileExtList, 
						function (strFileExt)
							return (not IsRealString(strFileExt)) or (not tipUtil:SetRegValue(strRoot, strDir, strFileExt, 1))
						end) 
			then
			bSuc = true
		end
	end
	
	Log("bSuc = " .. GTV(bSuc))
	return bSuc
end

function AdjustBaofengUserCfg(strCfgItemName, objCfgItemValue)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[AdjustBaofengUserCfg] " .. tostring(strMsg))
	end
	
	Log("strCfgItemName = " .. GTV(strCfgItemName) 
		.. ", objCfgItemValue = " .. GTV(objCfgItemValue) 
		.. "")
	local bSuc = false
	
	--TODO: HKEY_CURRENT_USER\Software\Baofeng\StormPlayer\UserConfig，当[CustomerSet]=1时表示总是保持关联
	if type(strCfgItemName) == "string" then
		local strRoot = "HKEY_CURRENT_USER"
		local strDir = "Software\\Baofeng\\StormPlayer\\UserConfig"
		if tipUtil:QueryRegKeyExists(strRoot, strDir) 
			and tipUtil:SetRegValue(strRoot, strDir, strCfgItemName, objCfgItemValue) 
			then
			bSuc = true
		end
	end
	
	Log("bSuc = " .. GTV(bSuc))
	return bSuc
end
	
function CheckBaofengAliveByWnd()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[CheckBaofengAliveByWnd] " .. tostring(strMsg))
	end
	
	local bBaofengAlive, uWndHandle = false, nil
	
	uWndHandle = tipUtil:FindWindow("Baofeng_StormPlayer", "暴风影音")
	if uWndHandle then
		bBaofengAlive = true
	end
	
	Log("bBaofengAlive = " .. GTV(bBaofengAlive) 
		.. ", uWndHandle = " .. GTV(uWndHandle) 
		.. "")
	return bBaofengAlive, uWndHandle
end

function ShutdownBaofeng(nTimeoutInMS, fCallback, bSucIfBaofengNotExist)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[ShutdownBaofeng] " .. tostring(strMsg))
	end
	
	Inc("ShutdownBaofeng(nTimeoutInMS = " .. GTV(nTimeoutInMS) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfBaofengNotExist = " .. GTV(bSucIfBaofengNotExist) 
		.. ")")
	Log("nTimeoutInMS = " .. GTV(nTimeoutInMS) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfBaofengNotExist = " .. GTV(bSucIfBaofengNotExist) 
		.. "")
	local bSuc = false
	local function CallbackWrapper()
		Log("bSuc = " .. GTV(bSuc))
		fCallback(bSuc)
		
		Dec("ShutdownBaofeng(nTimeoutInMS = " .. GTV(nTimeoutInMS) 
			.. ", fCallback = " .. GTV(fCallback) 
			.. ", bSucIfBaofengNotExist = " .. GTV(bSucIfBaofengNotExist) 
			.. ")")
	end
	
	if type(nTimeoutInMS) ~= "number" or nTimeoutInMS < 0 then
		CallbackWrapper()
		return
	end
	
	local bBaofengAlive, uWndHandle = CheckBaofengAliveByWnd()
	if not bBaofengAlive then
		if bSucIfBaofengNotExist then
			bSuc = true
		end
		CallbackWrapper()
		return
	end
	
	local nWM_SYSCOMMAND = 0x0112
	local nSMTO_ABORTIFHUNG = 0x0002
	local SMTO_BLOCK = 0x0001
	local nSC_CLOSE = 0xF060
	tipAsynUtil:SendMessageTimeout(uWndHandle, nWM_SYSCOMMAND, nSC_CLOSE, 0, 0x0003, nTimeoutInMS, 
		function (nRet)
			if 0 ~= nRet then
				CallbackWrapper()
				return
			end
			
			SetOnceTimer(1000 * 30, --继续等30s，让暴风从容退出
				function ()
					if not CheckBaofengAliveByWnd() then
						bSuc = true
					end
					
					CallbackWrapper()
				end)
		end)
end
	
function CheckBaofengPlatformAliveByWnd()
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[CheckBaofengPlatformAliveByWnd] " .. tostring(strMsg))
	end
	
	local bBaofengPlatformAlive, uWndHandle = false, nil
	
	uWndHandle = tipUtil:FindWindow("BaofengPlatform", "BaofengPlatform")
	if uWndHandle then
		bBaofengPlatformAlive = true
	end
	
	Log("bBaofengPlatformAlive = " .. GTV(bBaofengPlatformAlive) 
		.. ", uWndHandle = " .. GTV(uWndHandle) 
		.. "")
	return bBaofengPlatformAlive, uWndHandle
end

function ShutdownBaofengPlatform(nTimeoutInMS, fCallback, bSucIfBaofengPlatformNotExist)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[ShutdownBaofengPlatform] " .. tostring(strMsg))
	end
	
	Inc("ShutdownBaofengPlatform(nTimeoutInMS = " .. GTV(nTimeoutInMS) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfBaofengPlatformNotExist = " .. GTV(bSucIfBaofengPlatformNotExist) 
		.. ")")
	Log("nTimeoutInMS = " .. GTV(nTimeoutInMS) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. ", bSucIfBaofengPlatformNotExist = " .. GTV(bSucIfBaofengPlatformNotExist) 
		.. "")
	local bSuc = false
	local function CallbackWrapper()
		Log("bSuc = " .. GTV(bSuc))
		fCallback(bSuc)
		
		Dec("ShutdownBaofengPlatform(nTimeoutInMS = " .. GTV(nTimeoutInMS) 
			.. ", fCallback = " .. GTV(fCallback) 
			.. ", bSucIfBaofengPlatformNotExist = " .. GTV(bSucIfBaofengPlatformNotExist) 
			.. ")")
	end
	
	if type(nTimeoutInMS) ~= "number" or nTimeoutInMS < 0 then
		CallbackWrapper()
		return
	end
	
	local bBaofengPlatformAlive, uWndHandle = CheckBaofengPlatformAliveByWnd()
	if not bBaofengPlatformAlive then
		if bSucIfBaofengPlatformNotExist then
			bSuc = true
		end
		CallbackWrapper()
		return
	end
	
	local nWM_SYSCOMMAND = 0x0112
	local nSMTO_ABORTIFHUNG = 0x0002
	local SMTO_BLOCK = 0x0001
	local nSC_CLOSE = 0xF060
	tipAsynUtil:SendMessageTimeout(uWndHandle, nWM_SYSCOMMAND, nSC_CLOSE, 0, 0x0003, nTimeoutInMS, 
		function (nRet)
			if 0 ~= nRet then
				CallbackWrapper()
				return
			end
			
			SetOnceTimer(1000 * 30, --继续等30s，让暴风从容退出
				function ()
					if not CheckBaofengPlatformAliveByWnd() then
						bSuc = true
					end
					
					CallbackWrapper()
				end)
		end)
end

function ShellOpen(strFilePath, fCallback)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[ShellOpen] " .. tostring(strMsg))
	end
	
	Log("strFilePath = " .. GTV(strFilePath) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. "")
	
	local nTargetPID = nil
	local function CallbackWrapper()
		Log("nTargetPID = " .. GTV(nTargetPID))
		fCallback(nTargetPID)
	end
	
	if not IsRealString(strFilePath) then
		CallbackWrapper()
		return
	end
	
	local function FindRelatedPID(strFilePath)
		local tPIDList = {}
		local tImgList = {}
		
		local tProcList = tipUtil:GetProcessSnapshot()
		Foreach(tProcList, 
			function (tProc)
				local nPID = tProc["PID"]
				local strCmdLine, strImageFilePath = tipUtil:GetProcessCmdLine(nPID)
				if IsRealString(strCmdLine) and IsRealString(strFilePath) and string.find(strCmdLine, strFilePath, 1, true) then
					tPIDList[#tPIDList + 1] = nPID
					tImgList[#tImgList + 1] = strImageFilePath
				end
			end)
		
		return tPIDList, tImgList
	end
	
	local tPIDList, tImgList = FindRelatedPID(strFilePath)
	--Log("Before shell executing, we need to kill these processes: " .. List2String(tImgList) .. ", because we found our sample file path " .. strFilePath .. " in its commandline")
	--XLMB("在正式双击样本文件之前，需要先删除如下进程，因为它们的命令行中包含样本文件路径" .. strFilePath .. "\n" .. List2String(tImgList))
	local function KnK(i)
		--在双击strFilePath处的文件之前，先要将相关的进程杀掉（即那些命令行参数中包含strFilePath的进程）
		--然后双击strFilePath处的文件，获取相关进程的PID作为打开该文件的进程ID
		if i > #tPIDList then
			--XLMB("即将双击文件" .. strFilePath)
			tipUtil:ShellExecute(0, "open", strFilePath, 0, "", "SW_SHOWNORMAL")
			
			local tNewPIDList = FindRelatedPID(strFilePath)
			if #tNewPIDList >= 1 then
				nTargetPID = tNewPIDList[1]
			end
			
			CallbackWrapper()
			return
		end
		
		local nPID = tPIDList[i]
		NewKillProcess(nPID, 
			function (bOk)
				if not bOk then
					CallbackWrapper()
					return
				end
				
				KnK(i + 1)
			end)
	end
	KnK(1)
end

function CheckFileExtDefaultShellOpenCmd(strSampleFilePath, strExpectedExeFilePath, fCallback)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[CheckFileExtDefaultShellOpenCmd] " .. tostring(strMsg))
	end
	
	Log("strSampleFilePath = " .. GTV(strSampleFilePath) 
		.. ", strExpectedExeFilePath = " .. GTV(strExpectedExeFilePath) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. "")
	
	local bPassCheck = false
	local function CallbackWrapper()
		Log("bPassCheck = " .. GTV(bPassCheck))
		fCallback(bPassCheck)
	end
	
	if (not IsRealString(strSampleFilePath)) or (not IsRealString(strExpectedExeFilePath)) then
		CallbackWrapper()
		return
	end
	
	local function FindRelatedPID(strExeFilePath)
		local tPIDList = {}
		
		local tProcList = tipUtil:GetProcessSnapshot()
		Foreach(tProcList, 
			function (tProc)
				local nPID = tProc["PID"]
				local strCmdLine, strImageFilePath = tipUtil:GetProcessCmdLine(nPID)
				if IsRealString(strImageFilePath) and IsRealString(strExeFilePath) and string.lower(strImageFilePath) == string.lower(strExeFilePath) then
					tPIDList[#tPIDList + 1] = nPID
				end
			end)
		
		return tPIDList
	end
	
	local tPIDList = FindRelatedPID(strExpectedExeFilePath)
	--XLMB("在正式双击样本文件之前，需要先删除如下进程，因为它们的exe路径和我们期望的exe路径" .. strExpectedExeFilePath .. "一样\n" .. List2String(tPIDList))
	local function KnK(i)
		--在模拟双击strSampleFilePath处的文件之前，先要杀掉名为strExpectedExeFilePath的进程
		--然后双击该文件，看对应进程的Image File Path是否符合预期
		if i > #tPIDList then
			ShellOpen(strSampleFilePath, 
				function (nPID)
					if type(nPID) == "number" then
						local strCmdLine, strImageFilePath = tipUtil:GetProcessCmdLine(nPID)
						--XLMB("样本文件" .. GTV(strSampleFilePath) .. "是用" .. GTV(strImageFilePath) .. "打开的。我们期望用" .. strExpectedExeFilePath .. "打开")
						if IsRealString(strImageFilePath) 
							and IsRealString(strExpectedExeFilePath) 
							and string.lower(strImageFilePath) == string.lower(strExpectedExeFilePath) then
							bPassCheck = true
						end
						NewKillProcess(nPID, 
							function (bOk)
								CallbackWrapper()
							end)
					else
						CallbackWrapper()
					end
				end)
			return
		end
		
		local nPID = tPIDList[i]
		NewKillProcess(nPID, 
			function (bOk)
				if not bOk then
					CallbackWrapper()
					return
				end
				
				KnK(i + 1)
			end)
	end
	KnK(1)
end

--******************* custom **************************

function RunCaseStepByStep(tStepList, fCallback)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[RunCaseStepByStep] " .. tostring(strMsg))
	end
	
	Log("tStepList = " .. List2String(tStepList) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. "")
	
	local bSuc = false
	local function CallbackWrapper()
		Log("bSuc = " .. GTV(bSuc))
		fCallback(bSuc)
	end
	
	local function ValidateAndFormatStepList()
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[ValidateAndFormatStepList] " .. tostring(strMsg))
		end
		
		local bValid, tFuncList, tDescList = false, nil, nil
		
		if type(tStepList) == "table" then
			bValid, tFuncList, tDescList = true, {}, {}
			
			Foreach(tStepList, 
				function (tStep)
					local bStepValid = false
					
					if type(tStep) == "table" 
						and type(tStep["func"]) == "function" 
						and type(tStep["desc"]) == "string" 
						then
						tFuncList[#tFuncList + 1] = tStep["func"]
						tDescList[#tDescList + 1] = tStep["desc"]
						
						bStepValid = true
					end
					
					if not bStepValid then
						bValid = false
					end
				end)
		end
		
		Log("bValid = " .. GTV(bValid) 
			.. ", tFuncList = " .. GTV(tFuncList) 
			.. ", tDescList = " .. GTV(tDescList) 
			.. "")
		return bValid, tFuncList, tDescList
	end
	
	local bValid, tFuncList, tDescList = ValidateAndFormatStepList()
	if not bValid then
		CallbackWrapper()
		return
	end
	
	local function SnS(i)
		if i > #tStepList then
			bSuc = true
			CallbackWrapper()
			return
		end
		
		local fStep = tFuncList[i]
		local strDesc = tDescList[i]
		Log(">>>>>>>step-" .. tostring(i) .. ": [" .. tostring(strDesc) .. "] >>>>>>>>")
		XLMB(">>>>>>>step-" .. tostring(i) .. ": [" .. tostring(strDesc) .. "] >>>>>>>>")
		fStep(
			function (bOk)
				Log("<<<<<<<step-" .. tostring(i) .. ": [" .. tostring(strDesc) .. "] result: " .. tostring(bOk) .. " <<<<<<<<")
				XLMB("<<<<<<<step-" .. tostring(i) .. ": [" .. tostring(strDesc) .. "] result: " .. tostring(bOk) .. " <<<<<<<<")
				if not bOk then
					CallbackWrapper()
					return
				end
				
				SnS(i + 1)
			end)
	end
	
	SnS(1)
end

function TestCase(tCaseInput, fCallback)
	local OldLog = Log
	local function Log(strMsg)
		OldLog("[TestCase] " .. tostring(strMsg))
	end
	
	Log("tCaseInput = " .. GTV(tCaseInput) 
		.. ", fCallback = " .. GTV(fCallback) 
		.. "")
	
	local bSuc = false --测试结果
	local function CallbackWrapper()
		Log("bSuc = " .. GTV(bSuc))
		fCallback(bSuc)
	end
	
	local bXmpInstalled, strXmpExeFilePath, strXmpExeFileVersion, strMiniXmpExeFilePath, strMiniXmpExeFileVersion = false, nil, nil, nil, nil
	local bBaofengInstalled, strBaofengExeFilePath, strBaofengExeFileVersion = false, nil, nil
	local bCaseInputValid, tUniqFileExtList, tUniqFilePathList, strNewXmpStoreLocation, strOnlineVideoUrl, strVideoListFilePath = false, nil, nil, nil, nil, nil
	local tInputVideoList, tErrorVideoList = nil, nil
	
	local function Step_CheckXmpInstallation(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_CheckXmpInstallation] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		bXmpInstalled, strXmpExeFilePath, strXmpExeFileVersion, strMiniXmpExeFilePath, strMiniXmpExeFileVersion = IsXmpInstalled()
		if not bXmpInstalled then
			CallbackWrapper()
			return
		end
		
		bSuc = true
		CallbackWrapper()
	end
	
	local function Step_CheckBaofengInstallation(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_CheckBaofengInstallation] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		bBaofengInstalled, strBaofengExeFilePath, strBaofengExeFileVersion = IsBaofengInstalled()
		if not bBaofengInstalled then
			CallbackWrapper()
			return
		end
		
		bSuc = true
		CallbackWrapper()
	end
	
	local function Step_CheckCaseInput(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_CheckCaseInput] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
	
		local function ValidateAndFormatCaseInput()
			local OldLog = Log
			local function Log(strMsg)
				OldLog("[ValidateAndFormatCaseInput] " .. tostring(strMsg))
			end
			
			local bValid, tUniqFileExtList, tUniqFilePathList, strNewXmpStoreLocation, strOnlineVideoUrl, strVideoListFilePath = false, nil, nil, nil, nil, nil
			
			if type(tCaseInput) == "table" then
				if IsRealString(tCaseInput["video_list_file"]) and tipUtil:QueryFileExists(tCaseInput["video_list_file"]) then
					strVideoListFilePath = tCaseInput["video_list_file"]
					local strDat = tipUtil:ReadFileToString(strVideoListFilePath)
					if IsRealString(strDat) then
						tInputVideoList = SplitStringBySeperator(strDat, "\r\n", true)
						if #tInputVideoList >= 1 then
							tErrorVideoList = {}
							bValid = true
						end
					end
				end
			end
			
			Log("bValid = " .. GTV(bValid) 
				.. ", tUniqFileExtList = " .. List2String(tUniqFileExtList) 
				.. ", tUniqFilePathList = " .. List2String(tUniqFilePathList) 
				.. ", strNewXmpStoreLocation = " .. List2String(strNewXmpStoreLocation) 
				.. ", strOnlineVideoUrl = " .. List2String(strOnlineVideoUrl) 
				.. ", strVideoListFilePath = " .. List2String(strVideoListFilePath) 
				.. "")
			return bValid, tUniqFileExtList, tUniqFilePathList, strNewXmpStoreLocation, strOnlineVideoUrl, strVideoListFilePath
		end
		
		bCaseInputValid, tUniqFileExtList, tUniqFilePathList, strNewXmpStoreLocation, strOnlineVideoUrl, strVideoListFilePath = ValidateAndFormatCaseInput()
		if bCaseInputValid then
			bSuc = true
		end
		
		CallbackWrapper()
	end
	
	local function Step_ShutdownXmpIfExist(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ShutdownXmpIfExist] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		ShutdownXmp(1000 * 30, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				CallbackWrapper()
			end, 
			true)
	end
	
	local function Step_ShutdownXmp(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ShutdownXmp] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		ShutdownXmp(1000 * 30, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				
				CallbackWrapper()
			end)
	end
	
	local function Step_ShutdownMiniXmpIfExist(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ShutdownMiniXmpIfExist] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		ShutdownMiniXmp(1000 * 30, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				CallbackWrapper()
			end, 
			true)
	end
	
	local function Step_ShutdownMiniXmp(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ShutdownMiniXmp] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		ShutdownMiniXmp(1000 * 30, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				
				CallbackWrapper()
			end)
	end
	
	local function Step_ShutdownBaofengIfExist(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ShutdownBaofengIfExist] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		ShutdownBaofeng(1000 * 30, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				CallbackWrapper()
			end, 
			true)
	end
	
	local function Step_ShutdownBaofeng(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ShutdownBaofeng] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		ShutdownBaofeng(1000 * 30, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				
				CallbackWrapper()
			end)
	end
	
	local function Step_ShutdownBaofengPlatformIfExist(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ShutdownBaofengPlatformIfExist] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		ShutdownBaofengPlatform(1000 * 30, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				CallbackWrapper()
			end, 
			true)
	end
	
	local function Step_ShutdownBaofengPlatform(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ShutdownBaofengPlatform] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		ShutdownBaofengPlatform(1000 * 30, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				
				CallbackWrapper()
			end)
	end
	
	local function Step_AssocBaofeng(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_AssocBaofeng] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bOk = SetBaofengAssociatedFileExt(tUniqFileExtList)
		if not bOk then
			CallbackWrapper()
			return
		end
		
		bSuc = true
		CallbackWrapper()
	end
	
	local function Step_AlwaysAssocBaofeng(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_AlwaysAssocBaofeng] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bOk = AdjustBaofengUserCfg("CustomerSet", 1)
		if not bOk then
			CallbackWrapper()
			return
		end
		
		bSuc = true
		CallbackWrapper()
	end
	
	local function Step_AssocXmp(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_AssocXmp] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bOk = SetXmpAssociatedFileExt(tUniqFileExtList)
		if not bOk then
			CallbackWrapper()
			return
		end
		
		bSuc = true
		CallbackWrapper()
	end
	
	local function Step_AlwaysAssocXmp(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_AlwaysAssocXmp] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bOk = AdjustXmpUserCfg("bkeepassociation", true)
		if not bOk then
			CallbackWrapper()
			return
		end
		
		bSuc = true
		CallbackWrapper()
	end
	
	local function Step_AlwaysNotAssocXmp(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_AlwaysNotAssocXmp] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bOk = AdjustXmpUserCfg("bkeepassociation", false)
		if not bOk then
			CallbackWrapper()
			return
		end
		
		bSuc = true
		CallbackWrapper()
	end
	
	local function Step_SleepOneMin(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_SleepOneMin] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		SetOnceTimer(1000 * 60 * 1, 
			function ()
				bSuc = true
				
				CallbackWrapper()
			end)
	end
	
	local function Step_Sleep5s(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_Sleep5s] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		SetOnceTimer(1000 * 5, 
			function ()
				bSuc = true
				
				CallbackWrapper()
			end)
	end
	
	local function Step_Sleep10s(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_Sleep10s] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		SetOnceTimer(1000 * 10, 
			function ()
				bSuc = true
				
				CallbackWrapper()
			end)
	end
	
	local function Step_StartXmpWithTask(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_StartXmpWithTask] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local strCmdLine = "\"" .. tostring(strXmpExeFilePath) .. "\" \"" .. tostring(strOnlineVideoUrl) .. "\""
		CreateProcess(nil, strCmdLine, nil, 0x20, 1, 
			function (nPID)
				if type(nPID) ~= "number" or nPID <= 0 then
					CallbackWrapper()
					return
				end
				
				Log("nPID = " .. GTV(nPID))
				bSuc = true
				CallbackWrapper()
			end)
	end
	
	local function Step_StartXmp(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_StartXmp] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local strCmdLine = "\"" .. tostring(strXmpExeFilePath) .. "\""
		CreateProcess(nil, strCmdLine, nil, 0x20, 1, 
			function (nPID)
				if type(nPID) ~= "number" or nPID <= 0 then
					CallbackWrapper()
					return
				end
				
				Log("nPID = " .. GTV(nPID))
				bSuc = true
				CallbackWrapper()
			end)
	end
	
	local function Step_StartMiniXmp(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_StartMiniXmp] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local strCmdLine = "\"" .. tostring(strMiniXmpExeFilePath) .. "\" -embedding"
		CreateProcess(nil, strCmdLine, nil, 0x20, 1, 
			function (nXmpPID)
				if type(nXmpPID) ~= "number" or nXmpPID <= 0 then
					CallbackWrapper()
					return
				end
				
				bSuc = true
				CallbackWrapper()
			end)
	end
	
	local function Step_StartBaofeng(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_StartBaofeng] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		CreateProcess(nil, strBaofengExeFilePath, nil, 0x20, 1, 
			function (nBaofengPID)
				if type(nBaofengPID) ~= "number" or nBaofengPID <= 0 then
					CallbackWrapper()
					return
				end
				
				bSuc = true
				CallbackWrapper()
			end)
	end

	local function Step_VerifyBaofengAssoc(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_VerifyBaofengAssoc] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local function VnV(i)
			if i > #tUniqFilePathList then
				bSuc = true
				CallbackWrapper()
				return
			end
			
			local strFilePath = tUniqFilePathList[i]
			CheckFileExtDefaultShellOpenCmd(strFilePath, strBaofengExeFilePath, 
				function (bOk)
					if not bOk then
						CallbackWrapper()
						return
					end
					
					VnV(i + 1)
				end)
		end
		
		VnV(1)
	end
	
	local function Step_VerifyXmpAssoc(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_VerifyXmpAssoc] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local function VnV(i)
			if i > #tUniqFilePathList then
				bSuc = true
				CallbackWrapper()
				return
			end
			
			local strFilePath = tUniqFilePathList[i]
			CheckFileExtDefaultShellOpenCmd(strFilePath, strXmpExeFilePath, 
				function (bOk)
					if not bOk then
						CallbackWrapper()
						return
					end
					
					VnV(i + 1)
				end)
		end
		
		VnV(1)
	end
	
	local function Step_VerifyXmpNotAssoc(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_VerifyXmpNotAssoc] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local function VnV(i)
			if i > #tUniqFilePathList then
				bSuc = true
				CallbackWrapper()
				return
			end
			
			local strFilePath = tUniqFilePathList[i]
			CheckFileExtDefaultShellOpenCmd(strFilePath, strXmpExeFilePath, 
				function (bOk)
					if bOk then
						CallbackWrapper()
						return
					end
					
					VnV(i + 1)
				end)
		end
		
		VnV(1)
	end
	
	local function Step_KillXmpIfExist(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_KillXmpIfExist] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local nPID = tipUtil:GetProcessID("xmp.exe")
		if type(nPID) ~= "number" or nPID == 0 then
			bSuc = true
			CallbackWrapper()
			return
		end
		
		NewKillProcess(nPID, 
			function (bOk)
				if not bOk then
					CallbackWrapper()
					return
				end
				
				SetOnceTimer(1000 * 5, 
					function ()
						bSuc = true
						CallbackWrapper()
					end)
			end, 
			true)
	end
	
	local function Step_KillXmp(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_KillXmp] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local nPID = tipUtil:GetProcessID("xmp.exe")
		if type(nPID) ~= "number" or nPID == 0 then
			CallbackWrapper()
			return
		end
		
		NewKillProcess(nPID, 
			function (bOk)
				if not bOk then
					CallbackWrapper()
					return
				end
				
				SetOnceTimer(1000 * 5, 
					function ()
						bSuc = true
						CallbackWrapper()
					end)
			end)
	end
	
	local function Step_KillBaofengIfExist(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_KillBaofengIfExist] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bBaofengAlive, uWndHandle = CheckBaofengAliveByWnd()
		if not bBaofengAlive then
			bSuc = true
			CallbackWrapper()
			return
		end
		
		local nOwnerPID, nOwnerTID = tipUtil:GetWndProcessThreadId(uWndHandle) 
		if type(nOwnerPID) ~= "number" then
			bSuc = true
			CallbackWrapper()
			return
		end
		
		NewKillProcess(nOwnerPID, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				CallbackWrapper()
			end, 
			true)
	end
	
	local function Step_KillBaofeng(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_KillBaofeng] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bBaofengAlive, uWndHandle = CheckBaofengAliveByWnd()
		if not bBaofengAlive then
			CallbackWrapper()
			return
		end
		
		local nOwnerPID, nOwnerTID = tipUtil:GetWndProcessThreadId(uWndHandle) 
		if type(nOwnerPID) ~= "number" then
			CallbackWrapper()
			return
		end
		
		NewKillProcess(nOwnerPID, 
			function (bOk)
				if bOk then
					bSuc = true
				end
				CallbackWrapper()
			end)
	end
	
	local function Step_SetXmpExitOnClose(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_SetXmpExitOnClose] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bOk = AdjustXmpUserCfg("commont.closeafter", 2)
		if bOk then
			bSuc = true
		end
		
		CallbackWrapper()
	end
	
	local function Step_SetXmpMinOnClose(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_SetXmpMinOnClose] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local bOk = AdjustXmpUserCfg("commont.closeafter", 1)
		if bOk then
			bSuc = true
		end
		
		CallbackWrapper()
	end
	
	local function Step_ClickCloseBtn(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ClickCloseBtn] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local nCloseBtnId = "Xmp.MainFrame\\Xmp.MainObjectTree\\1000\\MainWnd.Root\\mainview\\mainwnd.shadow\\mainwnd.title\\button.close\\"
		--XMPQT.XMPHandler.Click(nCloseBtnId)
		XMPQT.API:Call("XMPCommand","/click ".. nCloseBtnId)
		bSuc = true
		CallbackWrapper()
	end
	
	local function Step_CheckXmpExit(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_CheckXmpExit] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local nPID = tipUtil:GetProcessID("xmp.exe")
		Log("nPID = " .. GTV(nPID))
		if type(nPID) ~= "number" or nPID == 0 then
			bSuc = true
		end
		
		CallbackWrapper()
	end
	
	local function Step_CheckXmpMin(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_CheckXmpMin] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local nPID = tipUtil:GetProcessID("xmp.exe")
		Log("nPID = " .. GTV(nPID))
		if type(nPID) == "number" and nPID ~= 0 then
			local uWndHandle = tipUtil:FindWindow("XLUEFrameHostWnd", "迅雷看看播放器")
			Log("uWndHandle = " .. GTV(uWndHandle))
			if uWndHandle then
				local bVisible = tipUtil:IsWindowVisible(uWndHandle)
				Log("bVisible = " .. GTV(bVisible))
				if not bVisible then
					bSuc = true
				end
			end
		end
		
		CallbackWrapper()
	end
	
	local function Step_DelNewXmpStoreDir(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_DelNewXmpStoreDir] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		bSuc = true
		if tipUtil:QueryFileExists(strNewXmpStoreLocation) then
			tipUtil:DeleteDir(strNewXmpStoreLocation)
			if tipUtil:QueryFileExists(strNewXmpStoreLocation) then
				bSuc = false
			end
		end
		
		CallbackWrapper()
	end
	
	local function Step_ChangeXmpStoreLocation(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_ChangeXmpStoreLocation] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local strRoot = "HKEY_LOCAL_MACHINE"
		local strDir = "SOFTWARE\\Thunder Network\\Xmp"
		local strValName = "StorePath"
		if tipUtil:QueryRegKeyExists(strRoot, strDir) 
			and tipUtil:SetRegValue(strRoot, strDir, strValName, strNewXmpStoreLocation) 
			then
			local strVal = tipUtil:QueryRegValue(strRoot, strDir, strValName)
			if IsRealString(strVal) and string.lower(strVal) == string.lower(strNewXmpStoreLocation) then
				bSuc = true
			end
		end
		
		CallbackWrapper()
	end
	
	local function Step_VerifyNewXmpStore(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_VerifyNewXmpStore] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		if tipUtil:QueryFileExists(strNewXmpStoreLocation) then
			bSuc = true
		end
		
		CallbackWrapper()
	end
	
	local function Step_BatchStartXmpWithTask(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_BatchStartXmpWithTask] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local function BnB(i)
			if i > #tInputVideoList then
				bSuc = true
				CallbackWrapper()
				return
			end
			
			local strVideoUrl = tInputVideoList[i]
			if not IsRealString(strVideoUrl) then
				XLMB("invalid url: [" .. tostring(i) .. "]" .. GTV(strVideoUrl) .. "")
				BnB(i + 1)
				return
			end
			
			Step_KillXmpIfExist(
				function ()
					local strCmdLine = "\"" .. tostring(strXmpExeFilePath) .. "\" \"" .. tostring(strVideoUrl) .. "\""
					CreateProcess(nil, strCmdLine, nil, 0x20, 1, 
						function (nPID)
							if type(nPID) ~= "number" or nPID <= 0 then
								tErrorVideoList[#tErrorVideoList + 1] = strVideoUrl
								XLMB("launch xmp failed: [" .. tostring(i) .. "]" .. GTV(strVideoUrl) .. "")
								BnB(i + 1)
								return
							end
							
							Log("nPID = " .. GTV(nPID))
							SetOnceTimer(1000 * 10, 
								function ()
									local bXmpPlaying = IsXmpPlaying()
									if bXmpPlaying then
										XLMB("ok : [" .. tostring(i) .. "]" .. GTV(strVideoUrl) .. "")
										BnB(i + 1)
										return
									end
									
									SetOnceTimer(1000 * 10, 
										function ()
											bXmpPlaying = IsXmpPlaying()
											if not bXmpPlaying then
												tErrorVideoList[#tErrorVideoList + 1] = strVideoUrl
												XLMB("play failed : [" .. tostring(i) .. "]" .. GTV(strVideoUrl) .. "")
											else
												XLMB("ok : [" .. tostring(i) .. "]" .. GTV(strVideoUrl) .. "")
											end
											
											BnB(i + 1)
										end)
								end)
						end)
				end)
		end
		
		BnB(1)
	end
	
	local function Step_SaveErrorVideoList(fCallback)
		local OldLog = Log
		local function Log(strMsg)
			OldLog("[Step_SaveErrorVideoList] " .. tostring(strMsg))
		end
		
		local bSuc = false
		local function CallbackWrapper()
			Log("bSuc = " .. GTV(bSuc))
			fCallback(bSuc)
		end
		
		local strDir = tipUtil:GetFileDir(strVideoListFilePath)
		local strVideoListFileNameWithoutExt, strVideoListFileExt = tipUtil:SplitFileNameAndExt(strVideoListFilePath)
		if (not IsRealString(strDir)) or (not IsRealString(strVideoListFileNameWithoutExt)) then
			CallbackWrapper()
			return
		end
		if type(strVideoListFileExt) ~= "string" then
			strVideoListFileExt = ""
		end
		local strErrorVideoListFilePath = tipUtil:PathCombine(strDir, "" .. tostring(strVideoListFileNameWithoutExt) .. "_err." .. tostring(strVideoListFileExt) .. "")
		if not IsRealString(strErrorVideoListFilePath) then
			CallbackWrapper()
			return
		end
		
		local strErrorVideoList = ""
		if #tErrorVideoList >= 1 then
			local i = 1
			strErrorVideoList = strErrorVideoList .. tostring(tErrorVideoList[i]) .. ""
		end
		for i = 2, #tErrorVideoList do
			strErrorVideoList = strErrorVideoList .. "\r\n" .. tostring(tErrorVideoList[i]) .. ""
		end
		
		tipUtil:WriteStringToFile(strErrorVideoListFilePath, strErrorVideoList)
		bSuc = true
		CallbackWrapper()
	end
	
	RunCaseStepByStep(
		{
			{func = Step_CheckXmpInstallation, desc = "检查xmp是否安装", }, 
			{func = Step_ShutdownXmpIfExist, desc = "退出xmp（如果存在的话）", }, 
			{func = Step_ShutdownMiniXmpIfExist, desc = "退出小xmp（如果存在的话）", }, 
			{func = Step_CheckCaseInput, desc = "检查用例输入是否合法", }, 
			{func = Step_BatchStartXmpWithTask, desc = "从任务清单中依次带任务启动xmp", }, 
			{func = Step_SaveErrorVideoList, desc = "保存播放失效的任务清单到文件", }, 
		}, 
		function (bOk)
			if bOk then
				bSuc = true
			end
			Step_KillXmpIfExist(
				function ()
					CallbackWrapper()
				end)
		end)
end

--******************* run **************************

function Main(strCmdLine)
	Inc("Main(" .. GTV(strCmdLine) .. ")")
	
	TestCase(
		{
			video_list_file = "\\\\192.168.13.159\\public\\lua自动化专用\\本地文件格式覆盖.txt", --输入checklist存放地址
		}, 
		function (bOk)
			Log("bOk = " .. GTV(bOk))
			XLMB("bOk = " .. GTV(bOk))
			if bOk then
				XMPQT.Output.Info("播放格式支持覆盖测试成功")
			else
				XMPQT.Output.Error("播放格式支持覆盖测试失败")
			end
		end)
	
	Dec("Main(" .. GTV(strCmdLine) .. ")")
end

Main("this is the command line")